#include <errno.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sqlite3.h>

#include "compat.h"

#include "psd_modules.h"
#include "psd_session.h"
#include "psd_config.h"
#include "psd_event.h"
#include "psd_measurement.h"
#include "psd_logger.h"
#include "psd_stats.h"

typedef struct psd_sqlite_temp_stats_t {
	psdSessionStats **stats;
	int count;
} psdSqliteTempStats;

psdModule *module_info();
int psd_sqlite_stats_init();
static void psd_sqlite_stats_read_config();
static int psd_sqlite_stats_save(psdSessionStats *stats);
static psdSessionStats **psd_sqlite_stats_read(double earliest_time, int max_count, int *num_stats);
static int psd_stats_sqlite_cb(void *arg, int count, char **values, char **headers);

static psdModule psd_sqlite_stats_module = {
	.desc = "SQLite Statistics Storage Module",
	.dependencies = "",
	.init = psd_sqlite_stats_init
};

struct psd_sqlite_stats_config_t {
	const char *database_file;
	const char *database_table;
};

static struct psd_sqlite_stats_config_t psdSQLiteStatsConfig = {
	.database_file = "phoebus.db",
	.database_table = "phoebus_stats"
};

psdStatsBackend sqlite_backend = {
	.read_stats = psd_sqlite_stats_read,
	.save_stats = psd_sqlite_stats_save,
};

psdModule *module_info() {
	return &psd_sqlite_stats_module;
}

int psd_sqlite_stats_init() {
	char query[2048];
	char *errmsg;
	sqlite3 *sql_conn;
	int create_table;
	int n;
	
	psd_sqlite_stats_read_config();

	if (sqlite3_open(psdSQLiteStatsConfig.database_file, &sql_conn) != SQLITE_OK) {
		psd_err(0, "couldn't open SQLite database");
		goto error_exit;
	}

	create_table = 0;
	snprintf(query, sizeof(query), "SELECT * FROM %s", psdSQLiteStatsConfig.database_table);
	n = sqlite3_exec(sql_conn, query, NULL, NULL, &errmsg);
	if (n == SQLITE_ERROR) {
		create_table = 1;
	} else if (n != SQLITE_OK) {
		psd_err(0, "error verifying database state: %s", errmsg);
		free(errmsg);
		goto error_exit2;
	}

	if (create_table) {
		snprintf(query, sizeof(query), "CREATE TABLE %s (session_id VARCHAR(32), user VARCHAR(30), email VARCHAR(30), institution VARCHAR(255), parent VARCHAR(255), child VARCHAR(255), end_time DOUBLE, duration DOUBLE, bytes_read BIGINT, bytes_written BIGINT, read_rate DOUBLE, write_rate DOUBLE)", psdSQLiteStatsConfig.database_table);
		if (sqlite3_exec(sql_conn, query, NULL, NULL, &errmsg)) {
			psd_err(0, "couldn't create statistics table: %s", errmsg);
			free(errmsg);
			goto error_exit2;
		}
	}

	sqlite3_close(sql_conn);

	if (psd_set_stats_backend(&sqlite_backend)) {
		psd_err(0, "couldn't register stats backend");
		goto error_exit2;
	}

	return 0;

error_exit2:
	sqlite3_close(sql_conn);
error_exit:
	return -1;
}

static void psd_sqlite_stats_read_config() {
	char *str_val;

	if (psd_depot_settings_get("sqlite_stats", "db_file", &str_val) == 0) {
		psdSQLiteStatsConfig.database_file = str_val;
	}

	if (psd_depot_settings_get("sqlite_stats", "db_table", &str_val) == 0) {
		psdSQLiteStatsConfig.database_table = str_val;
	}
}

static int psd_sqlite_stats_save(psdSessionStats *stats) {
	char query[2048];
	char *errmsg;
	static sqlite3 *sql_conn;

	if (sqlite3_open(psdSQLiteStatsConfig.database_file, &sql_conn) != SQLITE_OK) {
		psd_err(0, "couldn't open SQLite database");
		return -1;
	}

	snprintf(query, sizeof(query), "INSERT INTO %s VALUES('%s', '%s', '%s', '%s', '%s', '%s', %f, %f, %llu, %llu, %f, %f)",
			psdSQLiteStatsConfig.database_table,
			stats->session_id,
			stats->username,
			stats->email,
			stats->institution,
			stats->upstream_conns,
			stats->downstream_conns,
			stats->end_time,
			stats->duration,
			stats->bytes_read,
			stats->bytes_written,
			stats->read_rate,
			stats->write_rate);

	if (sqlite3_exec(sql_conn, query, NULL, NULL, &errmsg) != SQLITE_OK) {
		psd_err(0, "SQLite write failed: %s", errmsg);
		free(errmsg);
	}

	sqlite3_close(sql_conn);

	return 0;
}

static psdSessionStats **psd_sqlite_stats_read(double earliest_time, int max_count, int *num_stats) {
	char query[2048];
	char tmp[255];
	char *errmsg;
	static sqlite3 *sql_conn;
	psdSqliteTempStats ts;

	if (sqlite3_open(psdSQLiteStatsConfig.database_file, &sql_conn) != SQLITE_OK) {
		psd_err(0, "Couldn't open SQLite database");
		goto error_exit;
	}

	snprintf(query, sizeof(query), "SELECT * FROM %s", psdSQLiteStatsConfig.database_table);

	if (earliest_time != 0) {
		snprintf(tmp, sizeof(tmp), " WHERE end_time > %f", earliest_time);
		strlcat(query, tmp, sizeof(query));
	}

	if (max_count != 0) {
		snprintf(tmp, sizeof(tmp), " LIMIT %d", max_count);
		strlcat(query, tmp, sizeof(query));
	}

	ts.stats = NULL;
	ts.count = 0;

	if (sqlite3_exec(sql_conn, query, psd_stats_sqlite_cb, &ts, &errmsg) != SQLITE_OK) {
		psd_err(0, "Error running database query \"%s\": %s", query, errmsg);
		goto error_exit2;
	}

	sqlite3_close(sql_conn);

	*num_stats = ts.count;

	return ts.stats;

error_exit2:
	if (ts.stats) {
		int i;
		for(i = 0; i < ts.count; i++)
			psd_free_session_stats(ts.stats[i]);
	}
	sqlite3_close(sql_conn);
error_exit:
	return NULL;
}

static int psd_stats_sqlite_cb(void *arg, int count, char **values, char **headers) {
	int i;
	psdSqliteTempStats *ts = arg;
	psdSessionStats *stats;
	psdSessionStats **new_stats;

	stats = psd_session_stats_alloc();
	if (!stats) {
		psd_err(0, "allocating stats structure failed");
		goto error_exit;
	}

	new_stats = realloc(ts->stats, sizeof(psdSessionStats *) * (ts->count + 1));
	if (!new_stats) {
		psd_err(0, "realloc of stats structure failed");
		goto error_exit2;
	}

	for(i = 0; i < count; i++) {
		if (strcmp(headers[i], "session_id") == 0) {
			stats->session_id = strdup(values[i]);
		} else if (strcmp(headers[i], "user") == 0) {
			stats->username = strdup(values[i]);
		} else if (strcmp(headers[i], "email") == 0) {
			stats->email = strdup(values[i]);
		} else if (strcmp(headers[i], "institution") == 0) {
			stats->institution = strdup(values[i]);
		} else if (strcmp(headers[i], "parent") == 0) {
			stats->upstream_conns = strdup(values[i]);
		} else if (strcmp(headers[i], "child") == 0) {
			stats->downstream_conns = strdup(values[i]);
		} else if (strcmp(headers[i], "end_time") == 0) {
			stats->end_time = atof(values[i]);
		} else if (strcmp(headers[i], "duration") == 0) {
			stats->duration = atof(values[i]);
		} else if (strcmp(headers[i], "bytes_read") == 0) {
			stats->bytes_read = atoll(values[i]);
		} else if (strcmp(headers[i], "bytes_written") == 0) {
			stats->bytes_written = atoll(values[i]);
		} else if (strcmp(headers[i], "read_rate") == 0) {
			stats->read_rate = atof(values[i]);
		} else if (strcmp(headers[i], "write_rate") == 0) {
			stats->write_rate = atof(values[i]);
		}
	}

	if (!stats->session_id || !stats->username || !stats->email || !stats->institution || !stats->upstream_conns || !stats->downstream_conns) {
		psd_err(0, "partially filled stats structure");
		goto error_exit2;
	}

	new_stats[ts->count] = stats;
	ts->stats = new_stats;
	ts->count++;

	return 0;

error_exit2:
	psd_free_session_stats(stats);
error_exit:
	return -1;
}
